;;; packages.el --- Python Layer packages File for Spacemacs  -*- lexical-binding: nil; -*-
;;
;; Copyright (c) 2012-2025 Sylvain Benner & Contributors
;;
;; Author: Sylvain Benner <sylvain.benner@gmail.com>
;; URL: https://github.com/syl20bnr/spacemacs
;;
;; This file is not part of GNU Emacs.
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.


(defconst python-packages
  '(
    (blacken :toggle (eq 'black python-formatter))
    (code-cells :toggle (not (configuration-layer/layer-used-p 'ipython-notebook)))
    company
    cython-mode
    dap-mode
    (pet :toggle (eq python-virtualenv-management 'pet))
    eldoc
    evil-matchit
    flycheck
    ggtags
    helm-cscope
    (helm-pydoc :requires helm)
    (importmagic :toggle python-enable-importmagic)
    live-py-mode
    (nose :location (recipe :fetcher github :repo "syl20bnr/nose.el")
          :toggle (memq 'nose (flatten-list (list python-test-runner))))
    org
    pip-requirements
    (pipenv :toggle (memq 'pipenv python-enable-tools))
    (poetry :toggle (memq 'poetry python-enable-tools))
    (pippel :toggle (memq 'pip python-enable-tools))
    (uv :toggle (memq 'uv python-enable-tools)
        :location (recipe :fetcher github :repo "borgstad/uv.el" :files ("*.el")))
    py-isort
    pyenv-mode
    pydoc
    (pylookup :location (recipe :fetcher local))
    (python-pytest :toggle (memq 'pytest (flatten-list (list python-test-runner))))
    (python :location built-in)
    ;; Use the performance enhanced fork (https://github.com/jorgenschaefer/pyvenv/pull/128)
    (pyvenv :location (recipe :fetcher github :repo "sunlin7/pyvenv")
            :toggle (eq python-virtualenv-management 'pyvenv))
    (ruff-format :toggle (eq 'ruff python-formatter))
    semantic
    sphinx-doc
    smartparens
    xcscope
    window-purpose
    (yapfify :toggle (eq 'yapf python-formatter))
    ;; packages for anaconda backend
    (anaconda-mode :toggle (eq python-backend 'anaconda))
    (company-anaconda :requires (anaconda-mode company))
    ;; packages for Microsoft's pyright language server
    (lsp-pyright :requires lsp-mode :toggle (eq python-lsp-server 'pyright))))

(defun python/init-pet ()
  (use-package pet
    :hook (python-base-mode . pet-mode)))

(defun python/init-anaconda-mode ()
  (use-package anaconda-mode
    :defer t
    :init
    (setq anaconda-mode-installation-directory
          (concat spacemacs-cache-directory "anaconda-mode"))
    :config
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "hh" 'anaconda-mode-show-doc
      "ga" 'anaconda-mode-find-assignments
      "gu" 'anaconda-mode-find-references)
    (spacemacs|hide-lighter anaconda-mode)
    (define-advice anaconda-mode-goto (:before (&rest _) python/anaconda-mode-goto)
      (evil--jumps-push))
    (add-to-list 'spacemacs-jump-handlers-python-mode
                 '(anaconda-mode-find-definitions :async t))))

(defun python/init-code-cells ()
  (use-package code-cells
    :defer t
    :commands (code-cells-mode)
    :init (add-hook 'python-mode-hook 'code-cells-mode)
    :config (spacemacs/set-leader-keys-for-minor-mode 'code-cells-mode
              "gB" 'code-cells-backward-cell
              "gF" 'code-cells-forward-cell
              "sc" 'code-cells-eval
              "sa" 'code-cells-eval-above)))

(defun python/post-init-company ()
  ;; backend specific
  (add-hook 'python-mode-local-vars-hook #'spacemacs//python-setup-company)
  (spacemacs|add-company-backends
    :backends (company-files company-capf)
    :modes inferior-python-mode)
  (when (configuration-layer/package-used-p 'pip-requirements)
    (spacemacs|add-company-backends
      :backends company-capf
      :modes pip-requirements-mode)))

(defun python/init-company-anaconda ()
  (use-package company-anaconda
    :defer t))
;; see `spacemacs//python-setup-anaconda-company'

(defun python/init-blacken ()
  (use-package blacken
    :defer t
    :init
    (when python-format-on-save
      (add-hook 'python-mode-hook 'blacken-mode))
    :config (spacemacs|hide-lighter blacken-mode)))

(defun python/init-cython-mode ()
  (use-package cython-mode
    :defer t
    :config
    (when (eq python-backend 'anaconda)
      (spacemacs/set-leader-keys-for-major-mode 'cython-mode
        "hh" 'anaconda-mode-show-doc
        "gu" 'anaconda-mode-find-references))))

(defun python/pre-init-dap-mode ()
  (when (eq python-backend 'lsp)
    (add-to-list 'spacemacs--dap-supported-modes 'python-mode))
  (add-hook 'python-mode-local-vars-hook #'spacemacs//python-setup-dap))

(defun python/post-init-eldoc ()
  (add-hook 'python-mode-local-vars-hook #'spacemacs//python-setup-eldoc))

(defun python/post-init-evil-matchit ()
  (add-hook `python-mode-hook `turn-on-evil-matchit-mode))

(defun python/post-init-flycheck ()
  (spacemacs/enable-flycheck 'python-mode)
  ;; Setup flycheck but only after pet is loaded.
  (with-eval-after-load 'pet
    (add-hook 'python-mode-hook 'pet-flycheck-setup)))

(defun python/pre-init-helm-cscope ()
  (spacemacs|use-package-add-hook xcscope
    :post-init
    (spacemacs/setup-helm-cscope 'python-mode)))

(defun python/post-init-ggtags ()
  (add-hook 'python-mode-local-vars-hook #'spacemacs/ggtags-mode-enable))

(defun python/init-helm-pydoc ()
  (use-package helm-pydoc
    :defer t
    :init
    (spacemacs/set-leader-keys-for-major-mode 'python-mode "hd" 'helm-pydoc)))

(defun python/init-importmagic ()
  (use-package importmagic
    :defer t
    :init
    (add-hook 'python-mode-hook
              #'(lambda ()
                  ;; skip temp buffer which bufer-name begin with space
                  (unless (eq ?\s (string-to-char (buffer-name)))
                    (importmagic-mode))))
    (spacemacs|diminish importmagic-mode " ⓘ" " [i]")
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "rf" 'importmagic-fix-symbol-at-point)))

(defun python/init-live-py-mode ()
  (use-package live-py-mode
    :defer t
    :commands live-py-mode
    :init
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "l" 'live-py-mode)))

(defun python/init-nose ()
  (use-package nose
    :commands (nosetests-one
               nosetests-pdb-one
               nosetests-all
               nosetests-pdb-all
               nosetests-module
               nosetests-pdb-module
               nosetests-suite
               nosetests-pdb-suite)
    :init (spacemacs//bind-python-testing-keys)
    :config
    (add-to-list 'nose-project-root-files "setup.cfg")
    (setq nose-use-verbose nil)))

(defun python/pre-init-org ()
  (spacemacs|use-package-add-hook org
    :post-config (add-to-list 'org-babel-load-languages '(python . t))))

(defun python/pre-init-pipenv ()
  (add-to-list 'spacemacs--python-pipenv-modes 'python-mode))
(defun python/init-pipenv ()
  (use-package pipenv
    :defer t
    :commands (pipenv-activate
               pipenv-deactivate
               pipenv-shell
               pipenv-open
               pipenv-install
               pipenv-uninstall)
    :init
    (dolist (m spacemacs--python-pipenv-modes)
      (spacemacs/set-leader-keys-for-major-mode m
        "vpa" 'pipenv-activate
        "vpd" 'pipenv-deactivate
        "vpi" 'pipenv-install
        "vpo" 'pipenv-open
        "vps" 'pipenv-shell
        "vpu" 'pipenv-uninstall))))

(defun python/pre-init-pyenv-mode ()
  (add-to-list 'spacemacs--python-pyenv-modes 'python-mode))
(defun python/init-pyenv-mode ()
  (use-package pyenv-mode
    :if (executable-find "pyenv")
    :commands (pyenv-mode-versions)
    :init
    (pcase python-auto-set-local-pyenv-version
      ('on-visit
       (dolist (m spacemacs--python-pyenv-modes)
         (add-hook (intern (format "%s-hook" m))
                   'spacemacs//pyenv-mode-set-local-version)))
      ('on-project-switch
       (add-hook 'projectile-after-switch-project-hook
                 'spacemacs//pyenv-mode-set-local-version)))
    ;; setup shell correctly on environment switch
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "vu" 'pyenv-mode-unset
      "vs" 'pyenv-mode-set)))

(defun python/pre-init-poetry ()
  (add-to-list 'spacemacs--python-poetry-modes 'python-mode))
(defun python/init-poetry ()
  (use-package poetry
    :defer t
    :commands (poetry-venv-toggle
               poetry-tracking-mode)
    :init
    (dolist (m spacemacs--python-poetry-modes)
      (spacemacs/set-leader-keys-for-major-mode m
        "vod" 'poetry-venv-deactivate
        "vow" 'poetry-venv-workon
        "vot" 'poetry-venv-toggle))))


(defun python/init-pip-requirements ()
  (use-package pip-requirements
    :defer t))

(defun python/init-pippel ()
  (use-package pippel
    :defer t
    :init (spacemacs/set-leader-keys-for-major-mode 'python-mode
            "P" 'pippel-list-packages)
    :config
    (evilified-state-evilify-map pippel-package-menu-mode-map
      :mode pippel-package-menu-mode)))

(defun python/init-uv ()
  (use-package uv
    :defer t
    :init
    (spacemacs/declare-prefix-for-mode 'python-mode
      "u" "UV")
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "uv" 'uv
      "ua" 'uv-add
      "ud" 'uv-remove
      "ul" 'uv-lock
      "ue" 'uv-edit-pyproject-toml
      "ub" 'uv-build
      "up" 'uv-publish
      "un" 'uv-new
      "ui" 'uv-init
      "ur" 'uv-run)))

(defun python/init-py-isort ()
  (use-package py-isort
    :defer t
    :init
    (add-hook 'before-save-hook 'spacemacs//python-sort-imports)
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "rI" 'py-isort-buffer)))

(defun python/init-sphinx-doc ()
  (use-package sphinx-doc
    :defer t
    :init
    (add-hook 'python-mode-hook 'sphinx-doc-mode)
    (spacemacs/declare-prefix-for-mode 'python-mode "mS" "sphinx-doc")
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "Se" 'sphinx-doc-mode
      "Sd" 'sphinx-doc)
    :config (spacemacs|hide-lighter sphinx-doc-mode)))

(defun python/init-pydoc ()
  (use-package pydoc
    :defer t
    :init
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "hp" 'pydoc-at-point-no-jedi
      "hP" 'pydoc)))

(defun python/pre-init-pyvenv ()
  (add-to-list 'spacemacs--python-pyvenv-modes 'python-mode))
(defun python/init-pyvenv ()
  (use-package pyvenv
    :defer t
    :init
    (add-hook 'python-mode-hook #'pyvenv-tracking-mode)
    (pcase python-auto-set-local-pyvenv-virtualenv
      ('on-visit
       (dolist (m spacemacs--python-pyvenv-modes)
         (add-hook (intern (format "%s-hook" m))
                   'spacemacs//pyvenv-mode-set-local-virtualenv)))
      ('on-project-switch
       (add-hook 'projectile-after-switch-project-hook
                 'spacemacs//pyvenv-mode-set-local-virtualenv)))
    (dolist (m spacemacs--python-pyvenv-modes)
      (spacemacs/set-leader-keys-for-major-mode m
        "va" 'pyvenv-activate
        "vd" 'pyvenv-deactivate
        "vw" 'pyvenv-workon))
    ;; setup shell correctly on environment switch
    (dolist (func '(pyvenv-activate pyvenv-deactivate))
      (advice-add func :after 'spacemacs/python-setup-everything))))

(defun python/init-pylookup ()
  (use-package pylookup
    :commands (pylookup-lookup pylookup-update pylookup-update-all)
    :init
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "hH" 'pylookup-lookup)
    :config
    (evilified-state-evilify-map pylookup-mode-map
      :mode pylookup-mode)
    (let ((dir (configuration-layer/get-layer-local-dir 'python)))
      (setq pylookup-dir (concat dir "pylookup/")
            pylookup-program (concat pylookup-dir "pylookup.py")
            pylookup-db-file (concat pylookup-dir "pylookup.db")))
    (setq pylookup-completing-read 'completing-read)))

(defun python/init-python-pytest ()
  (use-package python-pytest
    :defer t
    :commands (python-pytest
               python-pytest-file
               python-pytest-file-dwim
               python-pytest-function
               python-pytest-last-failed
               python-pytest-repeat
               python-pytest-dispatch)
    :init
    ;; Reuse the generic testing bindings for a consistent UX.
    (spacemacs//bind-python-testing-keys)
    ;; Make the override robust per-buffer, regardless of load order.
    (add-hook 'python-mode-local-vars-hook
              #'spacemacs//python-pytest-set-root-from-setup-cfg)

    :config
    (advice-add #'python-pytest--get-buffer :around #'spacemacs/around-python-pytest--get-buffer)))

(defun python/init-python ()
  (use-package python
    :defer t
    :mode (("SConstruct\\'" . python-mode) ("SConscript\\'" . python-mode))
    :init
    (spacemacs/register-repl 'python
                             'spacemacs/python-start-or-switch-repl "python")
    (spacemacs//bind-python-repl-keys)
    (spacemacs//bind-python-formatter-keys)
    (spacemacs//python-lsp-set-up-format-on-save)
    (add-hook 'python-mode-local-vars-hook 'spacemacs//python-setup-backend)
    (add-hook 'python-mode-hook 'spacemacs//python-default)
    :config
    ;; add support for `ahs-range-beginning-of-defun' for python-mode
    (with-eval-after-load 'auto-highlight-symbol
      (add-to-list 'ahs-plugin-bod-modes 'python-mode))

    (spacemacs/declare-prefix-for-mode 'python-mode "mc" "execute")
    (spacemacs/declare-prefix-for-mode 'python-mode "md" "debug")
    (spacemacs/declare-prefix-for-mode 'python-mode "mh" "help")
    (spacemacs/declare-prefix-for-mode 'python-mode "mg" "goto")
    (spacemacs/declare-prefix-for-mode 'python-mode "ms" "REPL")
    (spacemacs/declare-prefix-for-mode 'python-mode "mr" "refactor")
    (spacemacs/declare-prefix-for-mode 'python-mode "mv" "virtualenv")
    (spacemacs/declare-prefix-for-mode 'python-mode "mvp" "pipenv")
    (spacemacs/declare-prefix-for-mode 'python-mode "mvo" "poetry")
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "'"  'spacemacs/python-start-or-switch-repl
      "cc" 'spacemacs/python-execute-file
      "cC" 'spacemacs/python-execute-file-focus
      "dt" 'spacemacs/python-toggle-breakpoint
      "gb" 'xref-go-back
      "ri" 'spacemacs/python-remove-unused-imports
      "sB" 'spacemacs/python-shell-send-buffer-switch
      "sb" 'spacemacs/python-shell-send-buffer
      "sE" 'spacemacs/python-shell-send-statement-switch
      "se" 'spacemacs/python-shell-send-statement
      "sF" 'spacemacs/python-shell-send-defun-switch
      "sf" 'spacemacs/python-shell-send-defun
      "si" 'spacemacs/python-start-or-switch-repl
      "sK" 'spacemacs/python-shell-send-block-switch
      "sk" 'spacemacs/python-shell-send-block
      "sn" 'spacemacs/python-shell-restart
      "sN" 'spacemacs/python-shell-restart-switch
      "sR" 'spacemacs/python-shell-send-region-switch
      "sr" 'spacemacs/python-shell-send-region
      "sL" 'spacemacs/python-shell-send-line-switch
      "sl" 'spacemacs/python-shell-send-line
      "ss" 'spacemacs/python-shell-send-with-output)

    ;; Set `python-indent-guess-indent-offset' to `nil' to prevent guessing `python-indent-offset
    ;; (we call python-indent-guess-indent-offset manually so python-mode does not need to do it)
    (setq-default python-indent-guess-indent-offset nil)

    ;; Emacs users won't need these key bindings
    ;; TODO: make these key bindings dynamic given the current style
    ;; Doing it only at init time won't update it if the user switches style
    ;; Also find a way to generalize these bindings.
    (when (eq dotspacemacs-editing-style 'vim)
      ;; the default in Emacs is M-n
      (define-key inferior-python-mode-map (kbd "C-j") 'comint-next-input)
      ;; the default in Emacs is M-p and this key binding overrides
      ;; default C-k which prevents Emacs users to kill line
      (define-key inferior-python-mode-map (kbd "C-k") 'comint-previous-input)
      ;; the default in Emacs is M-r; C-r to search backward old output
      ;; and should not be changed
      (define-key inferior-python-mode-map
                  (kbd "C-r") 'comint-history-isearch-backward)
      ;; this key binding is for recentering buffer in Emacs
      ;; it would be troublesome if Emacs user
      ;; Vim users can use this key since they have other key
      (define-key inferior-python-mode-map
                  (kbd "C-l") 'spacemacs/comint-clear-buffer))

    ;; add this optional key binding for Emacs user, since it is unbound
    (define-key inferior-python-mode-map
                (kbd "C-c M-l") 'spacemacs/comint-clear-buffer)

    (setq spacemacs--python-shell-interpreter-origin
          (eval (car (get 'python-shell-interpreter 'standard-value))))
    ;; setup the global variables for python shell if no custom value
    (when (equal python-shell-interpreter spacemacs--python-shell-interpreter-origin)
      (spacemacs//python-setup-shell default-directory)
      (setq spacemacs--python-shell-interpreter-origin python-shell-interpreter)
      (dolist (x '(python-shell-interpreter python-shell-interpreter-args))
        (set-default-toplevel-value x (symbol-value x))))))

(defun python/post-init-semantic ()
  (when (configuration-layer/package-used-p 'anaconda-mode)
    (add-hook 'python-mode-hook
              'spacemacs//disable-semantic-idle-summary-mode t))
  (add-hook 'python-mode-hook 'semantic-mode))

(defun python/pre-init-smartparens ()
  (spacemacs|use-package-add-hook smartparens
    :post-config
    (define-advice python-indent-dedent-line-backspace
        (:around (f &rest args) python/sp-backward-delete-char)
      (let ((pythonp (or (not smartparens-strict-mode)
                         (char-equal (char-before) ?\s))))
        (if pythonp
            (apply f args)
          (call-interactively 'sp-backward-delete-char))))))
(defun python/post-init-smartparens ()
  (add-hook 'inferior-python-mode-hook #'spacemacs//activate-smartparens))

(defun python/pre-init-xcscope ()
  (spacemacs|use-package-add-hook xcscope
    :post-init
    (spacemacs/set-leader-keys-for-major-mode 'python-mode
      "gi" 'cscope/run-pycscope)))

(defun python/init-yapfify ()
  (use-package yapfify
    :defer t
    :init
    (when python-format-on-save
      (add-hook 'python-mode-hook 'yapf-mode))
    :config (spacemacs|hide-lighter yapf-mode)))

(defun python/init-ruff-format ()
  (use-package ruff-format
    :defer t
    :init
    (when python-format-on-save
      (add-hook 'python-mode-hook 'ruff-format-on-save-mode))
    :config (spacemacs|hide-lighter ruff-format-on-save-mode)))

(defun python/init-lsp-pyright ()
  (use-package lsp-pyright
    :ensure nil
    :defer t))

(defun python/post-init-window-purpose ()
  (purpose-set-extension-configuration
   :python-layer
   (purpose-conf
    :mode-purposes '((inferior-python-mode . repl))
    :name-purposes '(("*compilation*" . logs)
                     ("*Pylookup Completions*" . help))
    :regexp-purposes '(("^\\*Anaconda" . help)
                       ("^\\*Pydoc" . help)
                       ("^\\*live-py" . logs)))))
